﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Web;
using System.Xml.Linq;
using System.Xml.XPath;
using RoketSolutions.SP2010.Infrostructure.Localization.Attributes;
using RoketSolutions.SP2010.Infrostructure.Localization.Providers.Interfaces;

namespace RoketSolutions.SP2010.Infrostructure.Localization.Providers
{
    public class ResourceProvider<T> : IResourceProvider<T>
    {
        private static readonly Dictionary<int, ResourceInfo> Cache = new Dictionary<int,ResourceInfo>();
        private static readonly object SynchObject = new object();

        #region / Properties /

        public ResourceConfig Configuration { get; set; }

        #endregion

        #region / Methods /

        public void Init(ResourceConfig config)
        {
            Configuration = config;
        }

        public string Get(Expression<Func<T, object>> propertyKey)
        {
            var resInfo = GetInfo(propertyKey);
            var resCulture = GetCulture();
            var resFile = FindResourceFile(resInfo, resCulture);
            if (resFile!= null)
            {
                var xml = LoadXmlData(resFile);
                var xmlElement = xml.XPathSelectElement(string.Format("/string[@name='{0}']", resInfo.ResourceKey));
                return xmlElement != null ? xmlElement.Value : string.Empty;
            }
            
            throw new Exception(string.Format("Resource file '{0}.resx' doesn't exist", resInfo.ResourceBaseName));
        }

        protected virtual FileInfo FindResourceFile(ResourceInfo resInfo, CultureInfo resCulture)
        {
            var resFolder = string.Empty;
            if (!string.IsNullOrEmpty(Configuration.ResourcesFolderPath))
            {
                resFolder = string.Format("{0}/", Configuration.ResourcesFolderPath.Trim('/'));
            }

            var resFiles = new List<string>();
            if (!string.IsNullOrEmpty(resCulture.Name))
            {
                resFiles.Add(string.Format("{0}{1}.{2}.xml", resFolder, resInfo.ResourceBaseName, resCulture.Name));
            }
            resFiles.Add(string.Format("{0}{1}.xml", resFolder, resInfo.ResourceBaseName));

            return resFiles.Select(filePath => new FileInfo(filePath)).FirstOrDefault(file => file.Exists);
        }

        private ResourceInfo GetInfo(Expression<Func<T, object>> propertyFunctor)
        {
            ResourceInfo info;
            MemberExpression memberExpression;

            var body = propertyFunctor.Body;
            if (body is UnaryExpression)
            {
                memberExpression = (((UnaryExpression)body).Operand as MemberExpression);
            }
            else
            {
                memberExpression = body as MemberExpression;
            }

            var key = memberExpression.Member.MetadataToken;
            if (!Cache.TryGetValue(key, out info))
            {
                lock (SynchObject)
                {
                    if (!Cache.TryGetValue(key, out info))
                    {
                        var propertyInfo = memberExpression.Member;
                        var type = propertyInfo.DeclaringType;

                        var fileAttribute = Attribute.GetCustomAttribute(type, typeof(ResourceFileAttribute), true) as ResourceFileAttribute;
                        var keyAttribute = Attribute.GetCustomAttribute(propertyInfo, typeof(ResourceKeyAttribute), true) as ResourceKeyAttribute;

                        info = new ResourceInfo
                                   {
                                       ResourceBaseName = fileAttribute != null ? fileAttribute.FileName : type.Name,
                                       ResourceKey = keyAttribute != null ? keyAttribute.Key : propertyInfo.Name
                                   };
                        Cache.Add(key, info);
                    }
                }
            }
            return info;
        }

        private CultureInfo GetCulture()
        {
            return Configuration.CurrentCulture ?? CultureInfo.CurrentUICulture;
        }

        private XElement LoadXmlData(FileInfo resFile)
        {
            XElement xml = null;
            if (HttpContext.Current != null)
            {
                xml = HttpContext.Current.Items[resFile.FullName] as XElement;
            }

            if (xml == null)
            {
                using (var stream = resFile.OpenText())
                {
                    xml = XElement.Load(stream);
                }
                if (HttpContext.Current != null)
                {
                    HttpContext.Current.Items.Add(resFile.FullName, xml);
                }
            }

            return xml;
        }

        #endregion

        #region / Nested Types /

        protected class ResourceInfo
        {
            public string ResourceBaseName { get; set; }
            public string ResourceKey { get; set; }
        }

        #endregion

    }
}